/**
 * Tungsten Scale-Out Stack
 * Copyright (C) 2013 Continuent Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Initial developer(s): Robert Hodges
 * Contributor(s):
 */

package com.continuent.tungsten.fsm.event.test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import com.continuent.tungsten.fsm.core.Action;
import com.continuent.tungsten.fsm.core.Entity;
import com.continuent.tungsten.fsm.core.Event;
import com.continuent.tungsten.fsm.core.Transition;
import com.continuent.tungsten.fsm.core.TransitionFailureException;
import com.continuent.tungsten.fsm.core.TransitionRollbackException;

/**
 * Sample class to test effect of actions that block including whether we are
 * interrupted.
 * 
 * @author <a href="mailto:robert.hodges@continuent.com">Robert Hodges</a>
 * @version 1.0
 */
public class BlockingAction implements Action
{
    public enum BlockingState
    {
        NOT_STARTED, STARTED, SUCCEEDED, INTERRUPTED;
    };

    private BlockingState  state      = BlockingState.NOT_STARTED;
    private BlockingState  finalState = null;
    private CountDownLatch ready      = new CountDownLatch(1);
    private CountDownLatch execute    = new CountDownLatch(1);
    private CountDownLatch done       = new CountDownLatch(1);

    /**
     * Get current state.
     */
    public synchronized BlockingState getState()
    {
        return state;
    }

    /**
     * Get outcome and reset for next run. You must call this for valid results
     * between executions of actions.
     */
    public synchronized BlockingState getFinalState()
    {
        return finalState;
    }

    /** Wait for blocking action to be ready, returning true if successful. */
    public boolean awaitReady(int waitMillis) throws InterruptedException
    {
        return ready.await(waitMillis, TimeUnit.MILLISECONDS);
    }

    /** The blocking action to proceed. */
    public void allowExecute()
    {
        execute.countDown();
    }

    /** Wait for blocking action to complete, returning true if successful. */
    public boolean awaitDone(int waitMillis) throws InterruptedException
    {
        return done.await(waitMillis, TimeUnit.MILLISECONDS);
    }

    /** Perform the blocking action. */
    public void doAction(Event event, Entity entity, Transition transition,
            int actionType) throws TransitionRollbackException,
            TransitionFailureException, InterruptedException
    {
        try
        {
            // Show that we are ready to run.
            state = BlockingState.STARTED;
            ready.countDown();

            // Await permission to run.
            execute.await();

            // Show that we are done.
            state = BlockingState.SUCCEEDED;
            done.countDown();
        }
        catch (InterruptedException e)
        {
            // Must rethrow or state machine will not notice the interrupt.
            state = BlockingState.INTERRUPTED;
            throw e;
        }
        finally
        {
            // Get read for next run.
            finalState = state;
            state = BlockingState.NOT_STARTED;
            ready = new CountDownLatch(1);
            execute = new CountDownLatch(1);
            done = new CountDownLatch(1);
        }
    }
}